package com.zrkizzy.seckill.controller.core;

import com.zrkizzy.seckill.annotation.AccessLimit;
import com.zrkizzy.seckill.entity.SeckillOrder;
import com.zrkizzy.seckill.entity.User;
import com.zrkizzy.seckill.enums.HttpStatusEnum;
import com.zrkizzy.seckill.enums.QueueStatusEnum;
import com.zrkizzy.seckill.utils.TimeUtil;
import com.zrkizzy.seckill.vo.rabbitmq.SeckillInfoVO;
import com.zrkizzy.seckill.service.core.IGoodsService;
import com.zrkizzy.seckill.service.core.IOrderService;
import com.zrkizzy.seckill.service.core.ISeckillOrderService;
import com.zrkizzy.seckill.service.tool.IMessageProducerService;
import com.zrkizzy.seckill.service.tool.IRedisService;
import com.zrkizzy.seckill.utils.JsonUtil;
import com.zrkizzy.seckill.vo.common.Result;
import com.zrkizzy.seckill.vo.system.GoodsVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.zrkizzy.seckill.constant.RabbitMqConst.SECKILL_EXCHANGE;
import static com.zrkizzy.seckill.constant.RabbitMqConst.SECKILL_ROUTER_KEY;
import static com.zrkizzy.seckill.constant.RedisConst.ORDER_PREFIX;
import static com.zrkizzy.seckill.constant.RedisConst.SECKILL_GOODS_PREFIX;

/**
 * 商品秒杀前端控制器
 *
 * @author zhangrongkang
 * @since 2023/3/22
 */
@Slf4j
@RestController
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
    @Autowired
    private RedisScript<Long> redisScript;

    @Autowired
    private IRedisService redisService;
    @Autowired
    private IMessageProducerService messageProducerService;

    @Autowired
    private IOrderService orderService;
    @Autowired
    private ISeckillOrderService seckillOrderService;
    @Autowired
    private IGoodsService goodsService;

    /**
     * 存储没有库存的商品ID
     */
    private final Map<Long, Boolean> emptyStockMap = new HashMap<>();

    /**
     * 获取秒杀接口的地址
     *
     * @param user 用户对象
     * @param goodsId 商品ID
     * @param kaptcha 验证码
     * @return 前端返回对象（秒杀接口的请求地址）
     */
    @AccessLimit(second = 30, needLogin = false, maxCount = 5)
    @GetMapping("/path")
    public Result path(User user, Long goodsId, String kaptcha) {
        if (null == user) {
            return Result.error(HttpStatusEnum.USER_EXIST_ERROR);
        }
        // 校验验证码
        Boolean check = orderService.checkKaptcha(user, goodsId, kaptcha);
        if (!check) {
            // 返回验证码错误
            return Result.error(HttpStatusEnum.KAPTCHA_ERROR);
        }
        String path = orderService.createPath(user, goodsId);
        return Result.success(path);
    }

    /**
     * 跳转到秒杀页面
     *
     * @return 公共返回对象
     */
    @PostMapping("/{path}/doSeckill")
    public Result doSeckill(@PathVariable String path, User user, Long goodsId) {
        if (user == null) {
            // 返回用户不存在结果
            return Result.error(HttpStatusEnum.USER_EXIST_ERROR);
        }
        // 判断当前传输的路径
        if (!orderService.checkPath(path, user, goodsId)) {
            // 返回非法请求结果
            return Result.error(HttpStatusEnum.REQUEST_ILLEGAL);
        }
        // 1. 判断是否重复抢购和当前商品是否具有库存
        SeckillOrder seckillOrder = redisService.get(ORDER_PREFIX + user.getId() + ":" + goodsId, SeckillOrder.class);
        if (null != seckillOrder) {
            // 返回重复秒杀结果
            return Result.error(HttpStatusEnum.ORDER_ERROR);
        }
        // 通过内存标记，减少Redis的访问
        if (emptyStockMap.get(goodsId)) {
            return Result.error(HttpStatusEnum.EMPTY_STOCK);
        }
        // 通过Lua脚本实现Redis预减库存操作
        Long stock = redisService.execute(redisScript, Collections.singletonList(SECKILL_GOODS_PREFIX + goodsId), Collections.EMPTY_LIST);
        // 如果预减后的库存数量小于0
        if (stock < 0) {
            // 修改当前商品在哈希表中的库存为空
            emptyStockMap.put(goodsId, Boolean.TRUE);
            // 将Redis中的商品数量一直保持0
            redisService.increment(SECKILL_GOODS_PREFIX + goodsId);
            return Result.error(HttpStatusEnum.EMPTY_STOCK);
        }
        // 创建秒杀消息对象
        SeckillInfoVO seckillInfoVO = SeckillInfoVO.builder()
                .user(user)
                .goodsId(goodsId)
                .build();
        // RabbitMQ发送秒杀信息
        messageProducerService.convertAndSend(SECKILL_EXCHANGE, SECKILL_ROUTER_KEY, JsonUtil.objectToJson(seckillInfoVO));
        return Result.success(QueueStatusEnum.QUEUEING.getCode());
    }

    /**
     * 系统初始化，将商品到库存数量加载到Redis
     *
     * @throws Exception 系统异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("---------------------- 添加商品库存到Redis中 ----------------------");
        List<GoodsVO> goodsVOList = goodsService.listGoods();
        if (CollectionUtils.isEmpty(goodsVOList)) {
            return;
        }
        goodsVOList.forEach(goodsVO -> {
            // 加载商品数量到Redis，过期时间为活动结束
            redisService.set(SECKILL_GOODS_PREFIX + goodsVO.getId(), goodsVO.getStockCount(), TimeUtil.secondIntervalNow(goodsVO.getEndTime()));
            // 将当前商品是否有库存添加到HashMap中
            emptyStockMap.put(goodsVO.getId(), Boolean.FALSE);
        });
        log.info("---------------------- 添加商品库存结束 ----------------------");
    }

    /**
     * 获取秒杀结果
     *
     * @param user 用户
     * @param goodsId 商品ID
     * @return 存在订单ID：成功； -1：秒杀失败； 0：排队中
     */
    @GetMapping("/result")
    public Result getResult(User user, Long goodsId) {
        log.info("---------------------- 执行获取秒杀结果 ----------------------");
        // 根据用户是否存在返回结果
        return null == user ?
                Result.error(HttpStatusEnum.USER_EXIST_ERROR) :
                // 如果存在用户则去查询当前秒杀结果
                Result.success(seckillOrderService.getResult(user, goodsId));
    }
}
